#! /usr/bin/env perl

###########################################################################
#
# vmerge
#
###########################################################################
#
# This command merges changes from a branch to the trunk, or from the trunk to a branch.  THIS COMMAND DOES
# NOT YET WORK WITH CVS!
#
# #########################################################################
#
# All the code herein is released under the Artistic License
#		( http://www.perl.com/language/misc/Artistic.html )
# Copyright (c) 2005-2009 Barefoot Software, Copyright (c) 2005-2005 ThinkGeek
#
###########################################################################

use strict;
use warnings;

use VCtools::Base;
use VCtools::Args;
use VCtools::Common;
use VCtools::Config;


#################################
# OPTIONS AND ARGUMENTS
#################################

VCtools::switch('branch', 'b', 'merge from this branch (default: merge from trunk)', 'branchname');
VCtools::switch('revision', 'V', 'merge changes up to this revision (default is HEAD)', 'revno');
VCtools::getopts();


#################################
# CHECK FOR ERRORS
#################################

VCtools::verify_files_and_group('.');
my $merge_commit = VCtools::check_branch_errors();
print STDERR "project is $VCtools::PROJ\n" if DEBUG >= 2;

# since check_branch_errors() has verified that we're at the TLD, we can just set @files to '.'
my @files = ('.');

VCtools::fatal_error("too dangerous to run vmerge with -N switch") if VCtools::no_outdated();

# get statuses for everything at once (quicker that way)
# like vbranch, vmerge is always recursive
VCtools::cache_file_status(@files, { DONT_RECURSE => 0, SHOW_BRANCHES => 1 });

# pull out which files are modified
my @mod_files = VCtools::get_all_with_status('modified');
print STDERR "mod files are: ", join(', ', @mod_files), "\n" if DEBUG >= 3;

if (VCtools::get_all_with_status('outdated'))
{
	VCtools::fatal_error("too dangerous to run vmerge with outdated files; please run vsync first");
}

my $from_branch = VCtools::branch();							# if we're merging from a branch, use that
if ($from_branch)
{
	VCtools::fatal_error("no such branch $from_branch") unless VCtools::branch_exists_in_vc($from_branch);
}
else
{
	$from_branch = 'TRUNK';
}
print STDERR "determined from branch to be $from_branch\n" if DEBUG >= 3;

my $to_branch = VCtools::get_branch('.') || 'TRUNK';
print STDERR "got $to_branch back from get_branch\n" if DEBUG >= 3;

print STDERR "to branch is $to_branch and from branch is $from_branch\n" if DEBUG >= 2;
if ($from_branch eq 'TRUNK' and $to_branch eq 'TRUNK')
{
	VCtools::fatal_error("cannot merge from trunk to trunk");
}
elsif ($from_branch eq $to_branch)
{
	VCtools::fatal_error("cannot merge from branch $from_branch to itself");
}

VCtools::prep_merge_tracking($from_branch, $to_branch);


#################################
# MAIN
#################################

use constant BAK_EXT => '.mrgbak';

VCtools::backup_full_project({ ext => BAK_EXT });

if (@mod_files)
{
	# we're just backing up these up out of sheer paranoia; most likely it will be completely unnecessary
	VCtools::create_backup_files(@mod_files, { ext => BAK_EXT });
}

VCtools::merge_from_branch();

# now figure out what got modified, if there are any conflicts, etc
CONFLICT_RESOLUTION:
{
	VCtools::cache_file_status(@files, { DONT_RECURSE => 0 });
	my @problem_files;
	push @problem_files, VCtools::get_all_with_status($_) foreach qw< conflict broken locked outdated >;
	if (@problem_files)
	{
		VCtools::info_msg("WARNING! There are problem files resulting from the merge.");
		VCtools::list_files("have one of the following statuses: conflict, broken, locked, outdated", @problem_files);
		VCtools::info_msg("The merge cannot continue unless these problems are resolved.");
		VCtools::info_msg("If you like, you can use another window to attempt to resolve the problems then say 'y' below.");
		if (VCtools::yesno("Do you want to check for problem files again?"))
		{
			redo CONFLICT_RESOLUTION;
		}

		if (yesno_no_default("Do you wish to restore the project to pre-merge status?"))
		{
			VCtools::restore_project_backup({ ext => BAK_EXT });
		}
		else
		{
			VCtools::info_msg("Your project is in an unstable state.  After further investigation, you may wish to");
			VCtools::info_msg(-INDENT => "manually restore the complete project backup located at "
					. VCtools::full_project_backup_name({ ext => BAK_EXT }));
		}

		exit 1;
	}
}
my @merged_files = get_files_to_commit();
print STDERR "files to be committed are: ", join(', ', @merged_files), "\n" if DEBUG >= 2;

VCtools::info_msg("NOTE! It is *strongly* advised that you go to another window and check the success of this operation.");
VCtools::info_msg(-INDENT => "If you feel the merge was successful, you should continue (say 'yes' below).");
VCtools::info_msg(-INDENT => "If you feel the merge failed, you should revert (say 'no' below) and try again later.");

if (yesno_no_default("Do you wish to continue by committing the merge operation?"))
{
	my $commit_msg = VCtools::merge_tracking_commit_msg();
	# HACK! when first arg removed from lib, remove here
	VCtools::commit_files(undef, @merged_files, { MERGE => scalar(@merged_files), MESSAGE => $commit_msg });
}
else
{
	# they don't like it; let's put everything back
	VCtools::restore_project_backup({ ext => BAK_EXT });
	exit 1;
}

print "done\n";


#################################
# SUBS
#################################


# a special version of yesno() that doesn't allow default values
sub yesno_no_default
{
	loop:
	{
		print "$_[0]  [y/n] ";
		my $answer = <STDIN>;

		redo loop unless $answer;
		return 1 if $answer =~ /^y/i;
		return 0 if $answer =~ /^n/i;
		redo loop;
	}
}


sub get_files_to_commit
{
	# first, turn our original mod_files into a hash
	my %mod_files = map { $_ => 1 } @mod_files;

	# now, find all the newly modified files
	my @new_mod_files = VCtools::get_all_with_status('modified');

	# now, subtract the originally modified files from the newly modified ones
	my @merged_files = grep { not exists $mod_files{$_} } @new_mod_files;

	# this should be the list of files which were successfully merged and ready to commit
	return @merged_files;
}
